﻿using System;
using System.Collections;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using yzrilyzr.util;
using Logger = yzrilyzr.util.Logger;

namespace FAQ_UI
{
	public class MyAdapterView : Panel
	{
		internal static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(IList), typeof(MyAdapterView), new UIPropertyMetadata(null, AdapterChange));
		internal static readonly DependencyProperty HolderProperty = DependencyProperty.Register("Holder", typeof(ViewHolder), typeof(MyAdapterView), new UIPropertyMetadata(null, AdapterChange));
		internal static readonly DependencyProperty StackProperty = DependencyProperty.Register("StackOrientation", typeof(StackOrientations), typeof(MyAdapterView), new UIPropertyMetadata(StackOrientations.TOP, AdapterChange));
		internal static readonly RoutedEvent ItemClickEvent = EventManager.RegisterRoutedEvent("ItemClick", RoutingStrategy.Bubble, typeof(OnItemClickEventHandler), typeof(MyAdapterView));
		internal static readonly RoutedEvent OnScrollEvent = EventManager.RegisterRoutedEvent("OnScroll", RoutingStrategy.Bubble, typeof(OnScrollEventHandler), typeof(MyAdapterView));
		protected object doListWorkLock = new object();
		protected double mCurrentValue;
		protected bool mDataChanged = false;
		protected double mDisplayOffset = 0;
		protected int mEndViewIndex = 0;

		//private MyHolderAdapter adapter;
		protected double mMaxValue = java.lang.Double.MAX_VALUE;

		protected double mNextValue;
		protected Recycler mRecycler = new Recycler();
		protected Scroller mScroller = new Scroller();
		protected int mStartViewIndex = -1;

		protected int mSyncPos = -2;

		public enum StackOrientations
		{
			TOP, BOTTOM, LEFT, RIGHT
		}

		public IList DataSource
		{
			get => (IList)GetValue(DataSourceProperty);
			set => SetValue(DataSourceProperty, value);
		}

		public ViewHolder Holder
		{
			get => (ViewHolder)GetValue(HolderProperty);
			set => SetValue(HolderProperty, value);
		}

		public StackOrientations StackOrientation
		{
			get => (StackOrientations)GetValue(StackProperty);
			set => SetValue(StackProperty, value);
		}

		protected static void AdapterChange(DependencyObject obj, DependencyPropertyChangedEventArgs r)
		{
			MyAdapterView control = (MyAdapterView)obj;
			control.Children.Clear();
			control.initView();
			control.onScrollChange();
			control.Background = Theme.BackBrush;
			control.ClipToBounds = true;
		}

		protected void addAndMeasureChild(UIElement child, int viewPos)
		{
			if (child == null) return;
			Size s = new Size(RenderSize.Width, RenderSize.Height);
			child.Measure(s);
			if (viewPos == -1) Children.Add(child);
			else Children.Insert(viewPos, child);
		}

		public ViewHolder createHolder()
		{
			//DT dt = new DT();
			//dt.getMs();
			var i = Holder.newInstance();
			//logger.i("createHolder", dt.getMs());
			return i;
		}

		public UIElement getChildAt(int index)
		{
			if (index >= Children.Count || index < 0) return null;
			return Children[index];
		}

		protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonUp(e);
			Point p = e.GetPosition(this);
			int i = tapOnUI(p);
			logger.i("tap", mStartViewIndex + 1 + i);
			if (i == -1) return;
			UIElement c = getChildAt(i);
			RoutedEventArgs newEvent = new OnItemClickEvent(ItemClickEvent, this, c, mStartViewIndex + 1 + i);
			this.RaiseEvent(newEvent);
		}

		private Logger logger = new Logger("AdapterView");

		private int tapOnUI(Point point)
		{
			for (int i = 0; i < Children.Count; i++)
			{
				UIElement child = getChildAt(i);
				if (isEventWithinView(point, child))
				{
					return i;
				}
			}
			return -1;
		}

		protected bool isEventWithinView(Point ev, UIElement child)
		{
			GeneralTransform gt = child.TransformToAncestor(this);
			Point point = gt.Transform(new Point(0, 0));
			double l = point.X;
			double t = point.Y;
			double r = l + child.RenderSize.Width;
			double b = t + child.RenderSize.Height;
			double x = ev.X;
			double y = ev.Y;
			return l < x && x < r && t < y && y < b;
		}

		protected override void OnMouseWheel(MouseWheelEventArgs e)
		{
			logger.i("OnMouseWheel", e);
			//base.OnMouseWheel(e);
			mScroller.forceFinished(true);
			//mScroller.fling(0, (int)mNextValue, 0, (int)(isStackInv()?e.Delta:-e.Delta)*5, 0, 0, -999999999, 999999999 /*(int)mMaxValue*/);
			//postChange();
			double sc = e.Delta;
			if (isStackInv()) mNextValue += sc;
			else mNextValue += -sc;
			//logger.i(mNextValue);
			//doListWork(sc);
			onScrollChange();
			e.Handled = true;
		}

		public int getCount()
		{
			if (DataSource == null) return 0;
			return DataSource.Count;
		}

		public UIElement getView(int p1, UIElement convertView)
		{
			ViewHolder hd;
			if (convertView == null)
			{
				hd = createHolder();
				convertView = hd.get();
			}
			else hd = (ViewHolder)convertView.GetValue(ViewHolder.k);
			hd.set(p1, DataSource[p1], DataSource);
			return convertView;
		}

		public void notifyDataChanged()
		{
			mDataChanged = true;
			onScrollChange();
		}

		public void notifyDataInvalidated()
		{
			Children.Clear();
			initView();
			onScrollChange();
		}

		public void setSelection(int position)
		{
			setSelectionFromTop(position, 0);
		}

		public void setSelectionFromTop(int position, int y)
		{
			if (getCount() == 0)
			{
				return;
			}
			if (position >= 0)
			{
				mSyncPos = Util.clamp(position, 0, getCount());
				onScrollChange();
			}
		}

		protected void doListWork(double dy)
		{
			lock (doListWorkLock)
			{
				removeNonVisibleItems(dy);
				fillList(dy);
				positionItems(dy);
			}
		}

		protected UIElement addViewHolderStart()
		{
			ViewHolder holder = createHolder();
			holder.set(mStartViewIndex, DataSource[mStartViewIndex], DataSource);
			UIElement child = holder.get();
			addAndMeasureChild(child, 0);
			mStartViewIndex--;
			return child;
		}

		protected UIElement addViewHolderEnd()
		{
			ViewHolder holder = createHolder();
			holder.set(mEndViewIndex, DataSource[mEndViewIndex], DataSource);
			UIElement child = holder.get();
			addAndMeasureChild(child, -1);
			mEndViewIndex++;
			return child;
		}

		protected virtual void fillList(double dy)
		{
		}

		protected bool isStackInv()
		{
			return StackOrientation == StackOrientations.BOTTOM || StackOrientation == StackOrientations.RIGHT;
		}

		protected bool isStackV()
		{
			return StackOrientation == StackOrientations.TOP || StackOrientation == StackOrientations.BOTTOM;
		}

		public event OnItemClickEventHandler ItemClick
		{
			add { this.AddHandler(ItemClickEvent, value); }
			remove { this.RemoveHandler(ItemClickEvent, value); }
		}

		public event OnScrollEventHandler OnScroll
		{
			add { this.AddHandler(OnScrollEvent, value); }
			remove { this.RemoveHandler(OnScrollEvent, value); }
		}

		protected void onScrollChange()
		{
			if (mDataChanged)
			{
				double oldCurrentY = mCurrentValue;
				initView();
				Children.Clear();
				mNextValue = oldCurrentY;
				mDataChanged = false;
				postChange();
			}
			if (mScroller.computeScrollOffset())
			{
				float scrollY = mScroller.getCurrY();
				mNextValue = scrollY;
			}
			if (mNextValue <= 0)
			{
				mNextValue = 0;
				mScroller.forceFinished(true);
			}
			if (mNextValue >= mMaxValue)
			{
				mNextValue = mMaxValue;
				mScroller.forceFinished(true);
			}
			double dy = isStackInv() ? mNextValue - mCurrentValue : mCurrentValue - mNextValue;
			if (mSyncPos != -2)
			{
				if (mSyncPos > mEndViewIndex)
				{
					UIElement child = getChildAt(0);
					mNextValue += child == null ? 10 : child.DesiredSize.Height;
					double a = mCurrentValue - mNextValue;
					if (isStackInv()) dy = -a;
					else dy = a;
					postChange();
				}
				else if (mSyncPos < mStartViewIndex + 1)
				{
					UIElement child = getChildAt(0);
					mNextValue -= child == null ? 10 : child.DesiredSize.Height;
					double a = mCurrentValue - mNextValue;
					if (isStackInv()) dy = -a;
					else dy = a;
					postChange();
				}
				else { mSyncPos = -2; }
			}

			doListWork(dy);
			mCurrentValue = mNextValue;
			if (!mScroller.isFinished())
			{
				postChange();
			}
			InvalidateVisual();
			RoutedEventArgs newEvent = new OnScrollEvent(OnScrollEvent, this, mNextValue, dy);
			this.RaiseEvent(newEvent);
		}

		protected virtual void positionItems(double dy)
		{
		}

		protected async void postChange()
		{
			await Task.Delay(1);
			onScrollChange();
		}

		protected virtual void removeNonVisibleItems(double dy)
		{
		}

		protected void initView()
		{
			mStartViewIndex = -1;
			mEndViewIndex = 0;
			mDisplayOffset = 0;
			mCurrentValue = 0;
			mNextValue = 0;
			mMaxValue = Double.MaxValue;
		}
	}

	public delegate void OnScrollEventHandler(object sender, OnScrollEvent e);

	public delegate void OnItemClickEventHandler(object sender, OnItemClickEvent e);

	public class OnScrollEvent : RoutedEventArgs
	{
		public double y;
		public double dy;

		public OnScrollEvent(RoutedEvent routedEvent, object source, double mNextValue, double dy) : base(routedEvent, source)
		{
			this.y = mNextValue;
			this.dy = dy;
		}
	}

	public class OnItemClickEvent : RoutedEventArgs
	{
		public UIElement child;
		public int index;

		public OnItemClickEvent(RoutedEvent routedEvent, object source, UIElement c, int v) : base(routedEvent, source)
		{
			this.child = c;
			this.index = v;
		}
	}
}